/*
 * Copyright 2006-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package asphalt2.container
{
	import asphalt2.event.ModulePageContainerEvent;
	import asphalt2.event.PageContainerEvent;
	import asphalt2.event.ProcessEvent;
	import asphalt2.util.ApplicationModal;
	import asphalt2.util.BusyCursor;
	import asphalt2.util.EventListenerHelper;
	import asphalt2.util.UIUtil;
	
	import flash.events.EventDispatcher;
	import flash.system.ApplicationDomain;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import mx.containers.ViewStack;
	import mx.core.Container;
	import mx.core.ContainerCreationPolicy;
	import mx.core.UIComponent;
	import mx.effects.Effect;
	import mx.effects.Fade;
	import mx.events.EffectEvent;
	import mx.events.FlexEvent;
	import mx.events.ModuleEvent;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.modules.IModuleInfo;
	import mx.modules.Module;
	import mx.modules.ModuleManager;
	
	use namespace pagecontainer_internal;

	[Effect(name="pageOutEffect")]
	[Effect(name="pageInEffect")]
	
	/**
	 * 処理開始時イベント
	 **/
	[Event(name="processStart", type="asphalt2.event.ProcessEvent")]
	/**
	 * 処理終了時イベント
	 **/
	[Event(name="processEnd", type="asphalt2.event.ProcessEvent")]
	
	/**
	 *  Dispatched by the backing ModuleInfo if there was an error during
	 *  module loading.
	 *
	 *  @eventType mx.events.ModuleEvent.ERROR
	 */
	[Event(name="error", type="mx.events.ModuleEvent")]
	
	/**
	 *  Dispatched by the backing ModuleInfo at regular intervals 
	 *  while the module is being loaded.
	 *
	 *  @eventType mx.events.ModuleEvent.PROGRESS
	 */
	[Event(name="progress", type="mx.events.ModuleEvent")]
	
	/**
	 *  Dispatched by the backing ModuleInfo once the module is sufficiently
	 *  loaded to call the <code>IModuleInfo.factory()</code> method and the
	 *  <code>IFlexModuleFactory.create()</code> method.
	 *
	 *  @eventType mx.events.ModuleEvent.READY
	 */
	[Event(name="ready", type="mx.events.ModuleEvent")]
	
	/**
	 * ページ表示用コンテナクラス
	 * PageContainerEventをディスパッチすると、このコンテナクラスが受取り、
	 *　画面を切り替えます。
	 * @author c9katayama
	 **/	
	public class PageContainer extends ViewStack
	{
		protected static var logger:ILogger = Log.getLogger(getQualifiedClassName(PageContainer).replace("::","."));
		
		/** ページが消えるときにエフェクト */
		private var _pageOutEffect:Effect;
		/** ページが表示されるときにエフェクト */
		private var _pageInEffect:Effect;
		/** historyManager利用時、スタックしておくViewの数*/
		private var _historyStackNum:int = 1;
		/**処理中にビジーアイコンを出すかどうか*/
		protected var _showBusyCursor:Boolean = true;
		/** 処理中にモーダルをかけるかどうか trueの場合、透明のモーダルをかけます*/
		protected var _doModal:Boolean = false;

		/**ModuleEventをdispatchするEventDispatcher
		 * addされたModuleEventはこのEventDispatcherにつけて、
		 * ModuleEvent発生時にこれを使用してDispatchします。*/
		protected var _moduleEventDispatcher:EventDispatcher;
		
		//内部処理用
		/** 処理中のPageContainerEvent */
		protected var processingEventObject:PageContainerEvent;
		/**　処理中かどうかのフラグ　*/
		protected var procesing:Boolean = false;
		/** ロード中のModuleInfo */
		protected var processingModuleInfo:IModuleInfo;
				
		public function set pageOutEffect(effect:Effect):void{
			_pageOutEffect = effect;
			if(effect != null){
				effect.suspendBackgroundProcessing=true;
			}
		}
		public function get pageOutEffect():Effect{			
			return _pageOutEffect;
		}
		public function set pageInEffect(effect:Effect):void{
			_pageInEffect = effect;
			setStyle("creationCompleteEffect",pageInEffect);
			if(effect != null){
				effect.suspendBackgroundProcessing=true;
			}
		}		
		public function get pageInEffect():Effect{
			return _pageInEffect;
		}
		public function set historyStackNum(num:int):void{			
			this._historyStackNum = num;
			if(num>1){
				historyManagementEnabled=true;
			}
		}
		public function get historyStackNum():int{
			return _historyStackNum;
		}
		public function set showBusyCursor(value:Boolean):void{
			this._showBusyCursor = value;
		}
		public function get showBusyCursor():Boolean{
			return _showBusyCursor;
		}
		public function set doModal(value:Boolean):void{
			this._doModal = value;
		}
		public function get doModal():Boolean{
			return _doModal;
		}
		public function PageContainer(){
			super();
			creationPolicy=ContainerCreationPolicy.ALL;
			UIUtil.addOnceEventListener(this,FlexEvent.CREATION_COMPLETE,
				function(e:FlexEvent):void{					
					setup();
				});
			setDefaultEffect();
		}
		protected function setDefaultEffect():void{
			//デフォルトのエフェクト
			var defaultPageOut:Fade = new Fade();
			defaultPageOut.alphaFrom = 1;
			defaultPageOut.alphaTo = 0;
			defaultPageOut.duration=300;
			defaultPageOut.suspendBackgroundProcessing=true;
			this.pageOutEffect = defaultPageOut;
			var defaultPageIn:Fade = new Fade();
			defaultPageIn.alphaFrom = 0;
			defaultPageIn.alphaTo = 1;
			defaultPageIn.duration=300;
			defaultPageIn.suspendBackgroundProcessing=true;
			this.pageInEffect = defaultPageIn;
		}
		protected function setup():void{
			UIUtil.findRoot().addEventListener(PageContainerEvent.NEXTPAGE,handleNextPage);
			UIUtil.callBackWhenComponentRemoved(this,handlePageContainerRemove);
			if(pageInEffect != null){
				pageInEffect.addEventListener(EffectEvent.EFFECT_END,handlePageInEffectEnd,false,0,true);
			}
			//エフェクト追加
			var children:Array = getChildren();
			for(var i:int = 0;i < children.length;i++){
				applyPageEffect(UIComponent(children[i]));
			}
		}
		protected function handlePageContainerRemove():void{
			if(Log.isDebug()){
				logger.debug("Container remove:"+getQualifiedClassName(this));
			}
			UIUtil.findRoot().removeEventListener(PageContainerEvent.NEXTPAGE,handleNextPage);
			
			this.removeAllChildren();
			_pageOutEffect = null;
			_pageInEffect = null;
			processEnd();
		}		
		
		
		protected function handleNextPage(e:PageContainerEvent):void{
			if(this.id == e.targetId){
				if(!procesing){
					processStart(e);
					if(e is ModulePageContainerEvent){
						loadNextModulePage(ModulePageContainerEvent(e));
					}else{
						loadNextPage(e);
					}
				}
			}		
		}
		/**
		 * ModuleEventのみ横取りします。
		 **/
		public override function addEventListener(type:String,listener:Function,useCapture:Boolean=false,priority:int=0,useWeakReference:Boolean=false):void{
			if(type==ModuleEvent.ERROR||type==ModuleEvent.PROGRESS||type==ModuleEvent.READY){
				if(_moduleEventDispatcher==null){
					_moduleEventDispatcher = new EventDispatcher();
				}
				_moduleEventDispatcher.addEventListener(type,listener,useCapture,priority,useWeakReference);				
			}else{
				super.addEventListener(type,listener,useCapture,priority,useWeakReference);
			}
		}
		
		protected function loadNextModulePage(modulePageEvent:ModulePageContainerEvent):void{
			processingModuleInfo = ModuleManager.getModule(modulePageEvent.modulePath);
			ModuleLoader.load(processingModuleInfo,this,modulePageEvent.nextPageClassName,modulePageEvent.nextPageProperties);
		}
		pagecontainer_internal function dispatchModuleEvent(e:ModuleEvent):void{
			if(_moduleEventDispatcher != null){
				_moduleEventDispatcher.dispatchEvent(e);				
			}
		}
		protected function loadNextPage(e:PageContainerEvent):void{
			var nextPage:Container = Container(new processingEventObject.nextPage());
			applyParameters(nextPage,processingEventObject.parameters);
			applyPageEffect(nextPage);
			handlePageReady(nextPage);			
		}		
		protected function applyPageEffect(child:UIComponent):void{
			var comp:UIComponent = child as UIComponent;
			var inEffect:Effect = comp.getStyle("showEffect") as Effect;
			if(inEffect == null){
				comp.setStyle("showEffect",pageInEffect);
			}else{
				inEffect.addEventListener(EffectEvent.EFFECT_END,handlePageInEffectEnd,false,0,true);
			}
			var outEffect:Effect = comp.getStyle("hideEffect") as Effect; 
			if(outEffect == null){
				comp.setStyle("hideEffect",pageOutEffect);
			}
		}
		protected function applyParameters(nextPage:Container,parameters:Object):void{
			if(parameters != null){
				try{
					if(nextPage.hasOwnProperty("parameters")){
						nextPage["parameters"] = parameters;
					}
					for(var key:String in parameters){
						if(nextPage.hasOwnProperty(key)){
							nextPage[key] = parameters[key];
						}
					}
				}catch(e:Error){
					processEnd();
					throw e;
				}
			}				
		}

		protected function processStart(e:PageContainerEvent):void{
			procesing = true;
			if(showBusyCursor){
				BusyCursor.show();
			}
			if(doModal){
				ApplicationModal.show();
			}
			processingEventObject = e;

			dispatchEvent(new ProcessEvent(ProcessEvent.START));
		}
		protected function processEnd():void{
			procesing = false;
			if(showBusyCursor){
				BusyCursor.hide();
			}
			if(doModal){
				ApplicationModal.hide();
			}
			processingEventObject = null;
			processingModuleInfo = null;
			dispatchEvent(new ProcessEvent(ProcessEvent.END));
		}
		protected function handlePageReady(nextPage:Container):void{			
			UIUtil.addOnceEventListener(nextPage,FlexEvent.CREATION_COMPLETE,handlePageCreateComplete);
			addChild(nextPage);
		}
		protected function handlePageCreateComplete(e:FlexEvent):void{
			var nextPage:Container = Container(e.target);
			if(selectedChild!=nextPage){
				//エフェクトないときはshowで消す
				if(pageInEffect==null){
					UIUtil.addOnceEventListener(nextPage,FlexEvent.SHOW,handleShow);
				}
				selectedChild = nextPage;//表示を自分に切り替え
			}else{
				//PageContainerに初期セットされたコンポーネントがなく、
				//かつはじめのコンポーネントが追加された時はselectedChild=nextPage
				if(pageInEffect == null){
					//この場合はShowはも呼ばれないので、処理終了
					handlePageInEffectEnd();
				}else{
					pageInEffect.play([nextPage]);
				}
			}
		}
		protected function handleShow(e:FlexEvent):void{
			handlePageInEffectEnd();			
		}
		protected function handlePageInEffectEnd(e:EffectEvent=null):void{
			//一定数以上は破棄
			var removeNum:int = getChildren().length-historyStackNum;
			for(var i:int = 0;i < removeNum;i++){
				removeChildAt(0);				
			}
			processEnd();
		}
		
		pagecontainer_internal function applyPageEffect(child:UIComponent):void{
			protected:applyPageEffect(child);
		}
		pagecontainer_internal function handlePageReady(nextPage:Container):void{
			protected:handlePageReady(nextPage);
		}
		pagecontainer_internal function applyParameters(nextPage:Container,parameters:Object):void{
			protected::applyParameters(nextPage,parameters);			
		}
		pagecontainer_internal function processEnd():void{
			protected::processEnd();
		}
		
	    //  Methods: IHistoryManagerClient
	    /**
	     * HistoryManagerをonの状態でViewStackからremoveすると、URLにViewStackに入っているよりも
	     * 大きなインデックスが記録されてしまう為、saveStateから最大履歴数以上のインデックスを返さないように
	     * する処理
	     */
	    public override function saveState():Object
	    {
	    	var maxIndex:int = historyStackNum-1;
	        var index:int = (selectedIndex>=maxIndex) ? maxIndex : selectedIndex;	        
	        return { selectedIndex: index };
	    }
	}
}
	import mx.modules.Module;
	import mx.events.ModuleEvent;
	import asphalt2.container.PageContainer;
	import asphalt2.util.EventListenerHelper;
	import mx.modules.IModuleInfo;
	import asphalt2.container.pagecontainer_internal;
	import mx.core.Container;
	import flash.utils.getDefinitionByName;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.modules.ModuleLoader;
	import mx.core.Application;
	import flash.system.ApplicationDomain;
	
	use namespace pagecontainer_internal;	

class ModuleLoader{

	private static var logger:ILogger = Log.getLogger("asphalt2.container.PageContainer.ModuleHandler");

	private var _moduleInfo:IModuleInfo;
	private var _container:PageContainer;	
	private var _nextPageClassName:String;
	private var _nextPageProperties:Object;
	
	public static function load(moduleInfo:IModuleInfo,container:PageContainer,nextPageClassName:String,nextPageParameters:Object):void{		
		var loader:ModuleLoader = new ModuleLoader();
		loader._moduleInfo = moduleInfo;
		loader._container = container;
		loader._nextPageClassName = nextPageClassName;
		loader._nextPageProperties = nextPageParameters;
		
		moduleInfo.addEventListener(ModuleEvent.ERROR,loader.handleModuleError);
		moduleInfo.addEventListener(ModuleEvent.PROGRESS,loader.handleModuleProgress);
		moduleInfo.addEventListener(ModuleEvent.READY,loader.handleModuleReady);
		moduleInfo.load(ApplicationDomain.currentDomain);
	}
	private function handleModuleError(e:ModuleEvent):void{			
		logger.error("Module load ERROR.Module="+_moduleInfo.url);
		_container.processEnd();
		_container.dispatchModuleEvent(e);
		dispose();
		throw e;
	}
	private function handleModuleProgress(e:ModuleEvent):void{
		_container.dispatchModuleEvent(e);
	}
	private function handleModuleReady(e:ModuleEvent):void{
		try{
			//nextPageClassNameがある場合も、CSSロードのために一旦インスタンス化
			var nextPage:Container = Module(e.module.factory.create());
			if(_nextPageClassName != null){
				var nextPageClass:Class = getDefinitionByName(_nextPageClassName) as Class;
				nextPage = Container(new nextPageClass());
			}
			//CSSのセレクタがかぶっていたりするとインスタンス化に失敗する
			if(nextPage == null){
				var errorMessage:String = "Cannot instanceate Module. module="+_moduleInfo.url+". Check MXML,AS,CSS.";
				logger.error(errorMessage);
				throw new Error(errorMessage);
			}
			_container.applyParameters(nextPage,_nextPageProperties);
			_container.applyPageEffect(nextPage);
			_container.handlePageReady(nextPage);
			_container.dispatchModuleEvent(e);
		}catch(error:Error){
			_container.processEnd();
			throw error;
		}finally{
			dispose();
		}
	}
	private function dispose():void{
		_moduleInfo.removeEventListener(ModuleEvent.ERROR,handleModuleError);
		_moduleInfo.removeEventListener(ModuleEvent.PROGRESS,handleModuleProgress);
		_moduleInfo.removeEventListener(ModuleEvent.READY,handleModuleReady);
		_moduleInfo = null;
		_container = null;	
		_nextPageClassName = null;
		_nextPageProperties = null;			
	}
}